/* 
 *   AQ_Field class defines a 56*56 field and 
 * banks of hand-cards and goal cards. Singleton 
 * pattern is adapted, because local games usually 
 * have only one running window.
 *   "field" is a place to put element cards. 
 * Because there can be at most 56 cards at a 
 * row(column), its maximum size is set 56*56. 
 * Its actual size is variable, using row and 
 * col to keep it.
 *   "bank" and "goal" are banks of AQ_HandCard 
 * and goal card (byte) objects respectively.
 *   addBank() adds a card into the rear of the 
 * bank.
 *   addGoal() adds a card into the rear of the 
 * bank of goal.
 *   at() returns the element card at specified 
 * position. If specification is out of bound, 
 * it returns null.
 *   connected() has 2 versions: 
 *   The static version counts the size of tree 
 * containing a specified element and linking 
 * by it.
 *   Another version counts the size of connected 
 * component including the starting point. Its 
 * answer is compared with the number of element 
 * cards to check that there is only a connected 
 * component on the field.
 *   getCardCount() returns the number of the current cards.
 *   getCol() return the number of the current columns.
 *   getRow() returns the number of current rows.
 *   initialize() sets initial condition for all 
 * players, including goal and hand cards.
 *   move() moves an element card to another place 
 * of the field.
 *   pollBank() polls a card from the head of the 
 * bank.
 *   pollGoal() polls a card from the head of the 
 * bank of goal.
 *   put() method inserts a card into field 
 * and returns the result of insertion. Note 
 * that the size of field may enlarge after 
 * a call. If "check" parameter is true, the 
 * new card can be inserted only if connected() 
 * returns yes after insertion.
 *   readObject() is an implementation for 
 * Serializable interface. This method is 
 * required because LinkedList members should 
 * be handled specially. If any exception occurs, 
 * the original data of this argument is kept.
 *   result() checks if there are 7 cards with
 * the same element are connected together. It 
 * returns an integer array containing indices 
 * of winners when the game ends. Note that an 
 * array is returned when card_count reaches 56, 
 * the maximum number of cards on the field.
 *   toScreen() prints the current field.
 *   writeObject() is the symmetric operation 
 * to readObject().
 *   zap() removes an specified element card 
 * from the field and return it. On failure, 
 * it returns null. If "check" parameter is 
 * true, the target card can be removed only 
 * if connected() returns yes after removal.
 */

import java.io.*;
import java.util.*;

import javax.swing.JFrame;

public class AQ_Field implements Serializable
{
	private static final LinkedList<AQ_HandCard> public_bank=new LinkedList<AQ_HandCard>();
	
	private static AQ_Field single_instance=null;
	
	private AQ_HandCard.ElementCard[][] field; // Field of element cards.
	
	private int card_count, row, col; // The field sizes row*col having card_count cards.
	
	private LinkedList<AQ_HandCard> bank;
	
	private LinkedList<AQ_Element> goal;
	
	public static AQ_Field getSingleInstance(final String source) throws IOException
	{
		if(single_instance==null)
		{
			BufferedReader card_data=new BufferedReader(new FileReader(source));
			String line;
			
			while((line=card_data.readLine())!=null)
			{
				String[] data=line.split("\\s+");
				byte[] e=new byte[4];
				
				if(line.length()>0 && data.length>0)
				{
					for(int i=0; i<data.length; i++)
						e[i]=Byte.parseByte(data[i]);
					
					public_bank.add(AQ_HandCard.getElementCard(e[0], e[1], e[2], e[3]));
				}
			}
			public_bank.add(AQ_HandCard.getUniversalCard());
			single_instance=new AQ_Field();
		}
		return single_instance;
	}
	
	public AQ_Field()
	{
		field=null;
		row=col=0;
		bank=null;
		goal=null;
	}
	
	public AQ_Field(final BufferedReader input) throws IOException
	{
		String line;
		field=new AQ_HandCard.ElementCard[56][];
		bank=new LinkedList<AQ_HandCard>();
		goal=new LinkedList<AQ_Element>();
		row=col=card_count=0;
		while((line=input.readLine())!=null){
			String[] data=line.split(" ");
			if(data.length<1) throw new IOException("Bad AQ_Field block.");
			else if(data[0].equals("field")){
				if(data.length==3 || data.length==7){
					int[] p=new int[]{Integer.parseInt(data[1]), Integer.parseInt(data[2])};
					byte[] e=null;
					if(data.length==7) e=new byte[]{Byte.parseByte(data[3]), Byte.parseByte(data[4]), Byte.parseByte(data[5]), Byte.parseByte(data[6])};
					while(row<p[0]+1) field[row++]=new AQ_HandCard.ElementCard[56];
					if(col<p[1]+1) col=p[1]+1;
					if(data.length==3) field[p[0]][p[1]]=AQ_HandCard.getUniversalCard();
					else field[p[0]][p[1]]=AQ_HandCard.getElementCard(e[0], e[1], e[2], e[3]);
					card_count++;
				}
				else throw new IOException("Bad field data.");
			}
			else if(data[0].equals("bank")){
				switch(data.length){
					case 1:
					bank.add(AQ_HandCard.getUniversalCard());
					break;
					case 2: // Action cards.
					bank.add(AQ_HandCard.getActionCard(Byte.parseByte(data[1])));
					break;
					case 5:
					bank.add(AQ_HandCard.getElementCard(Byte.parseByte(data[1]), Byte.parseByte(data[2]), Byte.parseByte(data[3]), Byte.parseByte(data[4])));
					break;
				}
			}
			else if(data[0].equals("goal")){
				for(int i=1; i<data.length; i++){
					byte e=Byte.parseByte(data[i]);
					goal.add(new AQ_Element(e));
				}
			}
			else if(data[0].equals("AQ_Field")){
				if(data.length==2 && data[1].equals("end")) break;
				else throw new IOException("Bad end of AQ_Field block.");
			}
			else throw new IOException("No such entry `"+data[0]+"'.");
		}
	}
	
	public void addBank(AQ_HandCard c)
	{
		bank.add(c);
	}
	
	public void addGoal(AQ_Element e)
	{
		goal.add(e);
	}
	
	public AQ_HandCard.ElementCard at(int r, int c)
	{
		if(r<0 || r>=row || c<0 || c>=col) 
			return null;		
		else 
			return field[r][c];
	}
	
	private static int connected(AQ_HandCard.ElementCard[][] f, final AQ_Element g, int j, int k)
	{
		AQ_HandCard.ElementCard t=f[j][k];
		int x=1;
		f[j][k]=null;
		if(j>0 && f[j-1][k]!=null &&((t.upleft().equals(g) && t.upleft().equals(f[j-1][k].downleft()))||(t.upright().equals(g) && t.upright().equals(f[j-1][k].downright())))) 
			x+=connected(f, g, j-1, k);
		if(k>0 && f[j][k-1]!=null &&((t.upleft().equals(g) && t.upleft().equals(f[j][k-1].upright()))||(t.downleft().equals(g) && t.downleft().equals(f[j][k-1].downright()))))
			x+=connected(f, g, j, k-1);
		if(j<f.length-1 && f[j+1][k]!=null &&((t.downleft().equals(g) && t.downleft().equals(f[j+1][k].upleft()))||(t.downright().equals(g) && t.downright().equals(f[j+1][k].upright())))) 
			x+=connected(f, g, j+1, k);
		if(k<f[j].length-1 && f[j][k+1]!=null &&((t.upright().equals(g) && t.upright().equals(f[j][k+1].upleft()))||(t.downright().equals(g) && t.downright().equals(f[j][k+1].downleft())))) 
			x+=connected(f, g, j, k+1);
		
		return x;
	}
	
	private int connected(AQ_HandCard.ElementCard[][] g, int r, int c)
	{
		if(g==field)
		{ // The first call from outside.
			AQ_HandCard.ElementCard[][] x=new AQ_HandCard.ElementCard[row][];
			
			for(int i=0; i<row; i++) 
				x[i]=g[i].clone();
			
			return connected(x, r, c);
		}
		else if(r<0 || r>=row || c<0 || c>=col || g[r][c]==null) 
			return 0;
		
		else
		{
			g[r][c]=null; // The modification affects each recursion.
			return 1+connected(g, r-1, c)+connected(g, r, c-1)+connected(g, r+1, c)+connected(g, r, c+1);
		}
	}
	public int getCardCount(){
		return card_count;
	}
	public int getCol(){
		return col;
	}
	public int getRow(){
		return row;
	}	
	public void initialize(AQ_Player[] player)
	{
		AQ_HandCard first_card=null;
		
		if(bank==null) 
			bank=new LinkedList<AQ_HandCard>(public_bank);
		else 
			bank.clear();
		
		if(goal==null) 
			goal=new LinkedList<AQ_Element>();
		else 
			goal.clear();
		
		for(int i=0; i<5; i++) 
			goal.add(AQ_Element.ELEMENT[i]);
		
		Collections.shuffle(goal);
		
		for(byte b: AQ_HandCard.ActionCard.ACTION) 
			for(int i=0; i<3; i++) 
				bank.add(new AQ_HandCard.ActionCard(b));
		
		Collections.shuffle(bank);
		
		for(int i=0; i<player.length; i++)
		{
			player[i]=new AQ_Player(goal.poll());
			for(int j=0; j<3; j++) 
				player[i].add(bank.poll());
		} // Every player gets 1 goal and 3 hand cards.
		
		for(;;)
		{ // The first element card will be polled.
			first_card=bank.poll();
			
			if(first_card instanceof AQ_HandCard.ElementCard)
				break;
			bank.add(first_card);
		}
		
		field=new AQ_HandCard.ElementCard[56][];
		field[0]=new AQ_HandCard.ElementCard[56];
		card_count=0;
		put((AQ_HandCard.ElementCard)first_card, 0, 0, false);
	}
	
	public boolean move(int s_r, int s_c, int d_r, int d_c, boolean r)
	{
		AQ_HandCard.ElementCard target=null;
		int old_row=row, old_col=col;
		
		if((target=zap(s_r, s_c, false))==null) 
			return false;
		
		if(row<old_row && s_r==0)
		{
			s_r--;
			d_r--;
		}
		
		if(col<old_col && s_c==0)
		{
			s_c--;
			d_c--;
		}
		
		if(r)
			target.rotate();
		
		if(put(target, d_r, d_c, true)<1)
		{
			if(r)
				target.rotate(); // On failure, undoing process is performed.
			put(target, s_r, s_c, false);
			
			return false; // Failure.
		}
		else 
			return true; // Success.
	}
	
	public AQ_HandCard pollBank()
	{
		return bank.poll();
	}
	
	public AQ_Element pollGoal()
	{
		return goal.poll();
	}
	
	public int put(AQ_HandCard.ElementCard n, int r, int c, boolean check)
	{
		int match=0; // Number of cards that the player gains after insertion.
		if(row==0 && col==0)
		{
			if(r!=0 || c!=0) 
				return 0; // Only (0, 0) is valid.
			field[0][0]=n;
			match=card_count=row=col=1;
		} // 1 is returned for success in spite of no adjacent card.
		else
		{
			TreeSet<AQ_Element> matching_element=new TreeSet<AQ_Element>();
			if(r<0)
			{
				if(c<0)
					return 0; // It is impossible that both r and c are negative.
				if(field[0][c]!=null)
				{
					if(n.universal()) 
						match=1;
					else
					{
						if(n.downleft().equals(field[0][c].upleft())) 
							matching_element.add(n.downleft());
						
						if(n.downright().equals(field[0][c].upright()))
							matching_element.add(n.downright());
						match=matching_element.size();
					}
					if(match>0)
					{ // At least one adjacent matching element.
						for(int i=field.length-2; i>0; i--)
							field[i]=field[i-1];
						field[0]=new AQ_HandCard.ElementCard[56];
						field[r=0][c]=n;
						row++; // The size enlarges.
						card_count++;
					}
				}
			}
			else if(c<0)
			{ // Only c is less than 0.
				if(field[r][0]!=null)
				{
					if(n.universal())
						match=1;
					else
					{
						if(n.upright().equals(field[r][0].upleft())) 
							matching_element.add(n.upright());
						
						if(n.downright().equals(field[r][0].downleft())) 
							matching_element.add(n.downright());
						match=matching_element.size();
					}
					if(match>0)
					{ // At least one adjacent matching element.
						for(int i=0; i<row; i++)
						{
							for(int j=field[i].length-2; j>0; j--) 
								field[i][j]=field[i][j-1];
							field[i][0]=null;
						}
						field[r][c=0]=n;
						col++; // The size enlarges.
						card_count++;
					}
				}
			}
			else if(r>=row)
			{
				if(c>=col) 
					return 0; // It is impossible that both r and c are out of bound.
				if(field[row-1][c]!=null)
				{
					if(n.universal()) 
						match=1;
					else
					{
						if(n.upleft().equals(field[row-1][c].downleft()))
							matching_element.add(n.upleft());
						
						if(n.upright().equals(field[row-1][c].downright()))
							matching_element.add(n.upright());
						
						match=matching_element.size();
					}
					if(match>0)
					{ // At least one adjacent matching element.
						field[row]=new AQ_HandCard.ElementCard[56];
						field[r=row][c]=n;
						row++; // The size enlarges.
						card_count++;
					}
				}
			}
			else if(c>=col)
			{ // Only c is less than 0.
				if(field[r][col-1]!=null)
				{
					if(n.universal()) 
						match=1;
					else
					{
						if(n.upleft().equals(field[r][col-1].upright()))
							matching_element.add(n.upleft());
						
						if(n.downleft().equals(field[r][col-1].downright())) 
							matching_element.add(n.downleft());
						
						match=matching_element.size();
					}
					if(match>0)
					{ // At least one adjacent matching element.
						field[r][c=col]=n;
						col++; // The size enlarges.
						card_count++;
					}
				}
			}
			else
			{
				if(field[r][c]==null)
				{
					if(r>0 && field[r-1][c]!=null)
					{
						if(n.universal())
							match=1;
						else
						{
							if(n.upleft().equals(field[r-1][c].downleft()) )
								matching_element.add(n.upleft());
							
							if(n.upright().equals(field[r-1][c].downright())) 
								matching_element.add(n.upright());
						}
					}
					if(c>0 && field[r][c-1]!=null)
					{
						if(n.universal())
							match=1;
						else
						{
							if(n.upleft().equals(field[r][c-1].upright())) 
								matching_element.add(n.upleft());
							
							if(n.downleft().equals(field[r][c-1].downright())) 
								matching_element.add(n.downleft());
						}
					}
					if(r<row-1 && field[r+1][c]!=null)
					{
						if(n.universal())
							match=1;
						else
						{
							if(n.downleft().equals(field[r+1][c].upleft())) 
								matching_element.add(n.downleft());
							
							if(n.downright().equals(field[r+1][c].upright()))
								matching_element.add(n.downright());
							
						}
					}
					if(c<col-1 && field[r][c+1]!=null)
					{
						if(n.universal())
							match=1;
						else
						{
							if(n.upright().equals(field[r][c+1].upleft())) 
								matching_element.add(n.upright());
							if(n.downright().equals(field[r][c+1].downleft()))
								matching_element.add(n.downright());
						}
					}
					if(!n.universal()) 
						match=matching_element.size();
					if(match>0)
					{
						field[r][c]=n;
						card_count++;
					}
				}
			}
			if(check && connected(field, r, c)!=card_count)
			{
				zap(r, c, false);
				match=0;
			}
		} // Note: The first needs no check.
		return match;
	}
	private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException
	{
		AQ_Field the_new=new AQ_Field();
		Object x=input.readObject();
		
		if(x instanceof AQ_HandCard.ElementCard[][]) the_new.field=(AQ_HandCard.ElementCard[][])x;
		else throw new ClassNotFoundException("No expected AQ_HandCard.ElementCard[][] class.");
		x=input.readObject();
		if(x instanceof Integer) the_new.card_count=(Integer)x;
		else throw new ClassNotFoundException("No expected Integer for card_count.");
		x=input.readObject();
		if(x instanceof Integer) the_new.row=(Integer)x;
		else throw new ClassNotFoundException("No expected Integer for row.");
		x=input.readObject();
		if(x instanceof Integer) the_new.col=(Integer)x;
		else throw new ClassNotFoundException("No expected Integer for col.");
		x=input.readObject();
		
		if(x instanceof LinkedList<?>)
		{
			LinkedList<?> y=(LinkedList<?>)x;
			the_new.bank=new LinkedList<AQ_HandCard>();
			
			for(int i=0; i<y.size(); i++)
			{
				if(y.get(i) instanceof AQ_HandCard) the_new.bank.push((AQ_HandCard)y.get(i));
				else throw new ClassNotFoundException("The list of card bank contains other objects rather than AQ_HandCard.");
			}
		}
		else throw new ClassNotFoundException("No expected LinkedList for bank.");
		
		x=input.readObject();
		if(x instanceof LinkedList<?>)
		{
			LinkedList<?> y=(LinkedList<?>)x;
			the_new.goal=new LinkedList<AQ_Element>();
			for(int i=0; i<y.size(); i++)
			{
				if(y.get(i) instanceof AQ_Element) the_new.goal.push((AQ_Element)y.get(i));
				else throw new ClassNotFoundException("The list contains other object rather than AQ_Element.");
			}
		}
		else throw new ClassNotFoundException("No expected LinkedList for goal card bank.");
		field=the_new.field;
		card_count=the_new.card_count;
		row=the_new.row;
		col=the_new.col;
		bank=the_new.bank;
		goal=the_new.goal;
	}
	
	public int[] result(final AQ_Player[] player)
	{
		int[] max_size=new int[AQ_Element.ELEMENT.length], winner=null;
		int max_count=0;
		AQ_HandCard.ElementCard[][] forest=null;
		
		for(int i=0; i<max_size.length; i++)
		{
			AQ_Element g=AQ_Element.ELEMENT[i];
			int size=0;
			forest=new AQ_HandCard.ElementCard[row][col];
			
			for(int j=0; j<row; j++)
				for(int k=0; k<col; k++) 
					if(field[j][k]!=null && field[j][k].contains(g)) 
						forest[j][k]=field[j][k];
			
			for(int j=0; j<row; j++)
			{
				for(int k=0; k<col; k++)
				{
					if(forest[j][k]!=null)
					{
						size=connected(forest, g, j, k);
						if(size>max_size[i]) 
							max_size[i]=size;
					}
				}
			}
		}
		max_count=max_size[0];
		
		for(int i=1; i<max_size.length; i++)
			if(max_size[i]>max_count) 
				max_count=max_size[i];
		
		if(max_count>=7 || card_count>=56)
		{
			int winner_count=0;
			
			for(int i=0; i<max_size.length; i++) 
				if(max_size[i]==max_count) 
					winner_count++;
			
			winner=new int[winner_count];
			
			for(int i=0, j=0; i<max_size.length; i++)
			{
				if(max_size[i]==max_count)
				{
					AQ_Element e=AQ_Element.ELEMENT[i];
					
					for(int k=0; k<player.length; k++)
						if(player[k].goal().equals(e))
							winner[j++]=k;
				}
			}
			if(winner_count>1) 
				Arrays.sort(winner);
		}
		return winner;
	}
	
	public void toScreen()
	{
		byte[][] element=null;
		
		for(int i=0; i<row; i++)
		{
			element=new byte[2][112];
			
			for(int j=0; j<col; j++)
			{
				if(field[i][j]!=null)
				{
					element[0][2*j]=field[i][j].upleft().element;
					element[0][2*j+1]=field[i][j].upright().element;
					element[1][2*j]=field[i][j].downleft().element;
					element[1][2*j+1]=field[i][j].downright().element;
				}
			}
			
			for(int j=0; j<2; j++)
			{
				for(int k=0; k<2*col; k++)
				{
					if(element[j][k]==0)
						System.out.print("  ");					
					else 
						System.out.printf("%d ", element[j][k]);
				}
				System.out.println("");
			}
		}
	}
	
	public void GUItoScreen(JFrame frame,FieldCard[][] fieldcard)
	{
		
		for(int i=0; i<row; i++)
		{
		
			for(int j=0; j<col; j++)
			{
				if(field[i][j]!=null)
				{
					
					fieldcard[i][j]=new FieldCard(i,j,field[i][j].upleft().element,field[i][j].upright().element,field[i][j].downleft().element,field[i][j].downright().element);
					frame.add(fieldcard[i][j]);
					fieldcard[i][j].repaint();
					
				}
			}
			
		}
		
	   frame.repaint();
	}
	
	public boolean tradeGoal(AQ_Player player)
	{
		if(goal.isEmpty()) 
			return false;
		
		goal.add(player.goal());
		player.setGoal(goal.poll());
		return true;
	}
	private void writeObject(ObjectOutputStream output) throws IOException{
		output.writeObject(field);
		output.writeObject(card_count);
		output.writeObject(row);
		output.writeObject(col);
		output.writeObject(bank);
		output.writeObject(goal);
	}
	public AQ_HandCard.ElementCard zap(int r, int c, boolean check)
	{
		if(r<0 || c<0 || r>=row || c>=col || field[r][c]==null) 
			return null;
		else
		{ // Removal can be classified into 6 cases.
			AQ_HandCard.ElementCard target=field[r][c];
			field[r][c]=null;
			card_count--;
			if(row==1 && col==1)
			{ // 1st: The only card is removed.
				field[0][0]=null;
				row=col=0;
			}
			else
			{
				int shrink=0;
				int[] old_size=new int[]{row, col};
				AQ_HandCard.ElementCard[][] old_field=new AQ_HandCard.ElementCard[56][];
				for(int i=0; i<row; i++) old_field[i]=field[i].clone();
				if(r==0 || r==row-1)
				{
					shrink=1;
					for(int i=0; shrink>0 && i<col; i++) if(field[r][i]!=null) shrink=0;
				}
				if(shrink==0 &&(c==0 || c==col-1))
				{
					shrink=2;
					for(int i=0; shrink>0 && i<row; i++) if(field[i][c]!=null) shrink=0;
				}
				switch(shrink)
				{
					case 1:
						if(r==0)
						{ // 2nd: Target card is in the top row.
							row--; // The field shrinks.
							for(int i=0; i<row; i++) field[i]=field[i+1];
								field[row]=null;
						}
						else if(r==row-1)
						{ // 4th: Target card is in the bottom row.
							field[--row]=null; // The field shrinks.
						}
						break;
					
					case 2:
						if(c==0)
						{ // 3rd: Target card is in the leftmost column.
							col--; // The field shrinks.
							for(int i=0; i<row; i++)
							{
								for(int j=0; j<col; j++) field[i][j]=field[i][j+1];
								field[i][col]=null;
							}
						}
						else if(c==col-1)
						{ // 5th: Target card is in the rightmost column.
							col--; // The field thrinks.
							for(int i=0; i<row; i++) field[i][col]=null;
						}
						break;
				}
				
				if(check)
				{
					int b=-1;
					for(int i=0; b<0 && i<col; i++) if(field[0][i]!=null) b=i;
					if(connected(field, 0, b)!=card_count)
					{
						card_count++;
						row=old_size[0];
						col=old_size[1];
						field=old_field;
						field[r][c]=target;
						target=null;
					}
				}
			} // Note: The first case needs no check.
			return target; // The 6th case needs no process.
		}
	}
}
